#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "func.h"

static inline void param_struct_value_serialize(PARAM *pa, char *out)
{
	if(pa->type.is_point)
	{
		sprintf(out, "\t%s_pack(&%s, &p);\n", pa->type.type_name, pa->name);
	}
	else
	{
		sprintf(out, "\t%s_pack(&%s, &p);\n", pa->type.name, pa->name);
	}
}

static void inline param_struct_point_serialize(PARAM *pa, char *out)
{
	if(pa->type.is_point)
	{
		sprintf(out, "\t%s_pack(%s, &p);\n", pa->type.type_name, pa->name);
	}
	else
	{
		sprintf(out, "\t%s_pack(%s, &p);\n", pa->type.name, pa->name);
	}
}

static void inline param_array_num_serailize(PARAM *pa, char *out) 
{
	sprintf(out, "\n\tARRAY_SIZE_pack(%s, &p);\n", pa->name);
}

static void inline param_array_serialize(PARAM *pa, PARAM *pnum, char *out)
{
	int  n;

	n = strlen(out);
	sprintf(out + n, "\tfor(i = 0; i < %s; i++)\n", pnum->name);

	n = strlen(out);
	sprintf(out + n, "\t{\n"); 

	n = strlen(out);
	sprintf(out + n, "\t\t%s one = %s + i;\n", pa->type.name, pa->name);
	
	n = strlen(out);

	if(pa->type.is_point)
	{
		sprintf(out + n, "\t\t%s_pack(one, &p);\n", pa->type.type_name);
	}
	else
	{
		sprintf(out + n, "\t\t%s_pack(one, &p);\n", pa->type.name);
	}

	strcat(out, "\t}\n"); 
}

static void inline param_data_serialize(PARAM *pa, PARAM *pnum, char *out)
{
	sprintf(out, "\tDATA_pack(%s, %s, &p);\n", pa->name, pnum->name);
}

static void inline gen_out_param_array_code(PARAM *pa, PARAM *num, char *out) 
{
	int n = strlen(out);

	if(pa->type.is_point)
	{
		sprintf(out + n, "\t%s_ARRAY_unpack(&p, %s, %s);\n", pa->type.type_name, pa->name, num->name);
	}
	else
	{
		sprintf(out + n, "\t%s_ARRAY_unpack(&p, %s, %s);\n", pa->type.name, pa->name, num->name);
	}
}

void	gen_out_param_code(func_t *func, char *out)
{
	int	n, count = 0;
	PARAM	*one;

	while(1)
	{
		one = func->pa_out + count;
		if(one->desc & PARAM_SINGLE)
		{
			n = strlen(out);
			if(one->type.is_point)
			{
				sprintf(out + n, "\t%s_unpack(&p, %s);\n", one->type.type_name, one->name);
			}
			else
			{
				sprintf(out + n, "\t%s_unpack(&p, %s);\n", one->type.name, one->name);
			}
			count++;
		}
		else if(one->desc & PARAM_MULTIP)
		{
			gen_out_param_array_code(one, one + 1, out);
			count += 2;
		}
		else if(one->desc & PARAM_DATA)
		{
			n = strlen(out);
			if((one + 1)->desc & PARAM_OUT)
			{
				sprintf(out + n, "\tDATA_unpack_copy(&p, %s, %s);\n", one->name, (one + 1)->name);
			}
			else
			{
				sprintf(out + n, "\tDATA_unpack2(&p, %s, %s);\n", one->name, (one + 1)->name);
			}
			count += 2;
		}
		else
		{
			assert(0);
		}
		if(count >= func->out_num)
		{
			break;
		}
	}
}

void	func_out_parse(func_t *func)
{
	int	count = 0;
	PARAM	*one, *cur;

	while(1)
	{
		one = func->pa + count;
		if(one->desc & PARAM_OUT)
		{
			if(one->desc & PARAM_SINGLE)
			{
				func->pa_out = (PARAM *)realloc(func->pa_out, sizeof(PARAM) * (func->out_num + 1));
				cur = func->pa_out + func->out_num;
				memset(cur, 0, sizeof(PARAM));
				memcpy(cur, one, sizeof(PARAM));
				func->out_num++;
				count++;
			}
			else if(one->desc & PARAM_MULTIP || one->desc & PARAM_DATA)
			{
				func->pa_out = (PARAM *)realloc(func->pa_out, sizeof(PARAM) * (func->out_num + 2));
				cur = func->pa_out + func->out_num;
				memset(cur, 0, sizeof(PARAM));
				memcpy(cur, one, sizeof(PARAM));

				cur = func->pa_out + func->out_num + 1;
				memset(cur, 0, sizeof(PARAM));
				memcpy(cur, one + 1, sizeof(PARAM));

				func->out_num += 2;
				count += 2;
			}
			else
			{
				assert(0);
			}
		}
		else
		{
			count++;
		}
		if(count >= func->pa_num)
		{
			break;
		}
	}
}

void	func_parse(func_t *func)
{
	func_out_parse(func);
}

void	func_gen_frontend(func_t *func, char *out)
{
	int	i, n = 0, count;
	PARAM	*pa;

	func_parse(func);

	sprintf(out, "%s\t", func->return_type.name);

	n = strlen(out);
	sprintf(out + n ,"%s\n", func->name);

	strcat(out, "(\n");

	for(i = 0; i < func->pa_num; i++)
	{
		pa = func->pa + i;
		n = strlen(out);
		sprintf(out + n, "\t%s ", pa->type.name);

		n = strlen(out);
		if(i != func->pa_num - 1)
		{
			sprintf(out + n, "%s,\n", pa->name);
		}
		else
		{
			sprintf(out + n, "%s\n", pa->name);
		}
	}

	strcat(out, ")\n");
	strcat(out, "{\n");

	/* function start */
	if(func->has_loop)
	{
		strcat(out, "\tint      ret, len, sz, i;\n");
	}
	else
	{
		strcat(out, "\tint      ret, len, sz;\n");
	}
	strcat(out, "\tuint8_t  *p, *rbf, *wbf;\n");

	strcat(out, "\tuint32_t result;\n\n");

	/* first param is session */
	n = strlen(out);
	sprintf(out + n, "\tsz  = MSG_RECV_BUFFER_SIZE(%s);\n", func->pa->name);
	n = strlen(out);
	sprintf(out + n, "\twbf = MSG_SEND_BUFFER_START(%s);\n", func->pa->name);
	n = strlen(out);
	sprintf(out + n, "\trbf = MSG_RECV_BUFFER_START(%s);\n", func->pa->name);

	n = strlen(out);
	sprintf(out + n, "\n\tMSG_ENCODE_INIT(wbf, &p, CMD_%s);\n", func->name);

	/* serialize start  */
	count = 1;
	while(1)
	{
		pa = func->pa + count;
		if(pa->desc & PARAM_IN)
		{
			n = strlen(out);
			if((pa->desc & PARAM_POINT) && (pa->desc & PARAM_MULTIP))
			{
				param_array_num_serailize(pa + 1, out + n);
				n = strlen(out);
				param_array_serialize(pa, pa + 1, out + n);
				count += 2;
			}
			else if((pa->desc & PARAM_POINT) && (pa->desc & PARAM_DATA))
			{
				param_data_serialize(pa, pa + 1, out + n);
				count += 2;
			}
			else
			{
				param_struct_point_serialize(pa, out + n);
				count++;
			}
		}
		else
		{
			count++;
		}
		if(count >= func->pa_num)
		{
			break;
		}
	}

	/* set lenlen */
	strcat(out, "\n\tlen = p - wbf - 2;\n");

	strcat(out, "\t*(uint16_t *)wbf = htons(len);\n");

	strcat(out, "\n\t/* send msg */\n");

	n = strlen(out);
	sprintf(out + n, "\tret = session_send(%s, wbf, len + 2);\n", func->pa->name);

	strcat(out, "\tif(ret <= 0) {\n\t\t return CKR_CHAN_WRITE;\n\t}\n");

	strcat(out, "\t/* recv msg */\n");

	n = strlen(out);
	sprintf(out + n, "\tret = session_recv(%s, rbf, sz);\n", func->pa->name);

	strcat(out, "\tif(ret <= 0) {\n\t\t return CKR_CHAN_READ;\n\t}\n");

	strcat(out, "\n\t/* decode */\n");

	strcat(out, "\tMSG_DECODE_INIT(rbf, &p, &result);\n");

	strcat(out, "\tif(result != CKR_OK) {\n\t\treturn result;\n\t}\n");

	/* decode return param */
	if(func->out_num > 0)
	{
		n = strlen(out);
		gen_out_param_code(func, out + n);
	}

	strcat(out, "\treturn CKR_OK;\n");
	strcat(out, "}");
}
